// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License 2.0;
// you may not use this file except in compliance with the Elastic License 2.0.

package v1beta1

import (
	"strconv"
	"strings"

	"github.com/pkg/errors"
	apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
	utilvalidation "k8s.io/apimachinery/pkg/util/validation"

	common_name "github.com/elastic/cloud-on-k8s/v3/pkg/controller/common/name"
)

const (
	configSecretSuffix                = "config"
	secureSettingsSecretSuffix        = "secure-settings"
	httpServiceSuffix                 = "http"
	elasticUserSecretSuffix           = "elastic-user"
	xpackFileRealmSecretSuffix        = "xpack-file-realm" //nolint:gosec
	internalUsersSecretSuffix         = "internal-users"
	unicastHostsConfigMapSuffix       = "unicast-hosts"
	licenseSecretSuffix               = "license"
	defaultPodDisruptionBudget        = "default"
	scriptsConfigMapSuffix            = "scripts"
	transportCertificatesSecretSuffix = "transport-certificates"

	controllerRevisionHashLen = 10
)

var (
	// ESNamer is a Namer that is configured with the defaults for resources related to an ES cluster.
	ESNamer = common_name.NewNamer("es")

	suffixes = []string{
		configSecretSuffix,
		secureSettingsSecretSuffix,
		httpServiceSuffix,
		elasticUserSecretSuffix,
		xpackFileRealmSecretSuffix,
		internalUsersSecretSuffix,
		unicastHostsConfigMapSuffix,
		licenseSecretSuffix,
		defaultPodDisruptionBudget,
		scriptsConfigMapSuffix,
		transportCertificatesSecretSuffix,
	}
)

// validateNames checks the validity of resource names that will be generated by the given Elasticsearch object.
func validateNames(es *Elasticsearch) error {
	if len(es.Name) > common_name.MaxResourceNameLength {
		return errors.Errorf("name exceeds maximum allowed length of %d", common_name.MaxResourceNameLength)
	}
	nodeSetNames := map[string]struct{}{}
	// validate ssets
	for _, nodeSet := range es.Spec.NodeSets {
		if _, ok := nodeSetNames[nodeSet.Name]; ok {
			return errors.Errorf("duplicated nodeSet name: '%s'", nodeSet.Name)
		}

		nodeSetNames[nodeSet.Name] = struct{}{}
		if errs := apimachineryvalidation.NameIsDNSSubdomain(nodeSet.Name, false); len(errs) > 0 {
			return errors.Errorf("invalid nodeSet name '%s': [%s]", nodeSet.Name, strings.Join(errs, ","))
		}

		ssetName, err := ESNamer.SafeSuffix(es.Name, nodeSet.Name)
		if err != nil {
			return errors.Wrapf(err, "error generating StatefulSet name for nodeSet: '%s'", nodeSet.Name)
		}

		// length of the ordinal suffix that will be added to the pods of this sset (dash + ordinal)
		podOrdinalSuffixLen := len(strconv.FormatInt(int64(nodeSet.Count), 10)) + 1
		// there should be enough space for the ordinal suffix and the controller revision hash
		if utilvalidation.LabelValueMaxLength-len(ssetName) < podOrdinalSuffixLen+controllerRevisionHashLen {
			return errors.Errorf("generated StatefulSet name '%s' exceeds allowed length of %d",
				ssetName,
				utilvalidation.LabelValueMaxLength-podOrdinalSuffixLen-controllerRevisionHashLen)
		}
	}

	// validate other suffixes
	for _, suffix := range suffixes {
		if _, err := ESNamer.SafeSuffix(es.Name, suffix); err != nil {
			return err
		}
	}

	return nil
}

// StatefulSet returns the name of the StatefulSet corresponding to the given NodeSet.
func StatefulSet(esName string, nodeSetName string) string {
	return ESNamer.Suffix(esName, nodeSetName)
}

func ConfigSecret(ssetName string) string {
	return ESNamer.Suffix(ssetName, configSecretSuffix)
}

func SecureSettingsSecret(esName string) string {
	return ESNamer.Suffix(esName, secureSettingsSecretSuffix)
}

func TransportCertificatesSecret(esName string) string {
	return ESNamer.Suffix(esName, transportCertificatesSecretSuffix)
}

func HTTPService(esName string) string {
	return ESNamer.Suffix(esName, httpServiceSuffix)
}

func ElasticUserSecret(esName string) string {
	return ESNamer.Suffix(esName, elasticUserSecretSuffix)
}

func XPackFileRealmSecret(esName string) string {
	return ESNamer.Suffix(esName, xpackFileRealmSecretSuffix)
}

func InternalUsersSecret(esName string) string {
	return ESNamer.Suffix(esName, internalUsersSecretSuffix)
}

// UnicastHostsConfigMap returns the name of the ConfigMap that holds the list of seed nodes for a given cluster.
func UnicastHostsConfigMap(esName string) string {
	return ESNamer.Suffix(esName, unicastHostsConfigMapSuffix)
}

func ScriptsConfigMap(esName string) string {
	return ESNamer.Suffix(esName, scriptsConfigMapSuffix)
}

func LicenseSecretName(esName string) string {
	return ESNamer.Suffix(esName, licenseSecretSuffix)
}

func DefaultPodDisruptionBudget(esName string) string {
	return ESNamer.Suffix(esName, defaultPodDisruptionBudget)
}
